package com.gitee.sop.gatewaycommon.loadbalancer;

import com.gitee.sop.gatewaycommon.bean.SopConstants;
import com.gitee.sop.gatewaycommon.manager.EnvironmentKeys;
import com.gitee.sop.gatewaycommon.param.ApiParam;
import lombok.Data;
import org.apache.commons.lang3.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.*;
import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
import org.springframework.cloud.loadbalancer.core.SelectedInstanceCallback;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.http.HttpHeaders;
import org.springframework.util.StringUtils;
import reactor.core.publisher.Mono;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

@Data
public class GrayLoadBalancer implements ReactorServiceInstanceLoadBalancer {
    private static final Logger log = LoggerFactory.getLogger(GrayLoadBalancer.class);
    private final String serviceId;
    private AtomicInteger position; // 位置，下标
    private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;

    public GrayLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, String serviceId) {
        this.serviceId = serviceId;
        this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
        this.position = new AtomicInteger(new Random().nextInt(1000));  //随机进行设置一个值
    }

    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        // 提供备选的服务实例列表
        ServiceInstanceListSupplier supplier = this.serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);
        // 选择服务实例
        return supplier.get(request).next().map((serviceInstances) -> this.processInstanceResponse(supplier, serviceInstances, request));
    }

    private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier,
                                                              List<ServiceInstance> serviceInstances,
                                                              Request request) {
        // 从备选的服务列表中选择一个具体的服务实例
        Response<ServiceInstance> serviceInstanceResponse = this.getInstanceResponse(serviceInstances,
                request);
        if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
            ((SelectedInstanceCallback) supplier).selectedServiceInstance((ServiceInstance) serviceInstanceResponse.getServer());
        }
        return serviceInstanceResponse;
    }

    private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances,
                                                          Request request) {
        // 实例为空   首先进行实例判空
        if (instances.isEmpty()) {
            if (log.isWarnEnabled()) {
                //判空后 给予警告
                log.warn("No servers available for service: " + this.serviceId);
            }
            //返回响应
            return new EmptyResponse();
        } else {
            // 存放预发服务器
            List<ServiceInstance> preServers = new ArrayList<>(4);
            // 存放灰度发布服务器
            List<ServiceInstance> grayServers = new ArrayList<>(4);
            // 存放非预发服务器
            List<ServiceInstance> notPreServers = new ArrayList<>(4);

            notPreServers.addAll(grayServers);

            for (ServiceInstance instance : instances) {
                // 获取实例metadata
                Map<String, String> metadata = instance.getMetadata();
                // 是否开启了预发模式
                if (this.isPreServer(metadata)) {
                    preServers.add(instance);
                } else if (this.isGrayServer(metadata)) {
                    grayServers.add(instance);
                } else {
                    notPreServers.add(instance);
                }
            }
            notPreServers.addAll(grayServers);

            RequestDataContext context = (RequestDataContext) request.getContext();
            ApiParam apiParam = new ApiParam();
            apiParam.putAll(context.getClientRequest().getAttributes());

            // 如果没有开启预发布服务和灰度发布，直接用默认的方式
            if (preServers.isEmpty() && grayServers.isEmpty()) {
                int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;
                ServiceInstance instance = instances.get(pos % instances.size());
                return new DefaultResponse(instance);
            }
            // 如果是从预发布域名访问过来，则认为是预发布请求，选出预发服务器
            if (this.isRequestFromPreDomain(context.getClientRequest().getHeaders())) {
                int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;
                ServiceInstance instance = preServers.get(pos % instances.size());
                return new DefaultResponse(instance);
            }
            // 如果是灰度请求，则认为是灰度用户，选出灰度服务器
            if (apiParam.fetchGrayRequest()) {
                int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;
                ServiceInstance instance = grayServers.get(pos % instances.size());
                return new DefaultResponse(instance);
            }
            return getInstanceResponse(instances);
        }
    }

    /**
     * 是否是预发布服务器
     *
     * @param metadata metadata
     * @return true：是
     */
    private boolean isPreServer(Map<String, String> metadata) {
        return Objects.equals(metadata.get(SopConstants.METADATA_ENV_KEY), SopConstants.METADATA_ENV_PRE_VALUE);
    }

    /**
     * 是否是灰度发布服务器
     *
     * @param metadata metadata
     * @return true：是
     */
    private boolean isGrayServer(Map<String, String> metadata) {
        return Objects.equals(metadata.get(SopConstants.METADATA_ENV_KEY), SopConstants.METADATA_ENV_GRAY_VALUE);
    }

    /**
     * 通过判断hostname来确定是否是预发布请求
     *
     * @param httpHeaders
     * @return 返回true：可以进入到预发环境
     */
     boolean isRequestFromPreDomain(HttpHeaders httpHeaders) {
        String domain = EnvironmentKeys.PRE_DOMAIN.getValue();
        if (StringUtils.isEmpty(domain)) {
            return false;
        }
        String[] domains = domain.split("\\,");
         if (httpHeaders.getHost() != null) {
             String host = httpHeaders.getHost().getHostName();
             return ArrayUtils.contains(domains, host);
         } else {
             return false;
         }
    }

    public Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
        if (instances.isEmpty()) {
            if (log.isWarnEnabled()) {
                log.warn("No servers available for service: " + serviceId);
            }
            return new EmptyResponse();
        }

        // Do not move position when there is only 1 instance, especially some suppliers
        // have already filtered instances
        if (instances.size() == 1) {
            return new DefaultResponse(instances.get(0));
        }

        // Ignore the sign bit, this allows pos to loop sequentially from 0 to
        // Integer.MAX_VALUE
        int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;

        ServiceInstance instance = instances.get(pos % instances.size());

        return new DefaultResponse(instance);
    }
}